﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using Aeon.Emulator.DebugSupport;

namespace Aeon.Emulator.Decoding.Recompiler
{
    /// <summary>
    /// Generates linear addresses from 32-bit addressing modes.
    /// </summary>
    internal sealed class Addresser32
    {
        #region Private Fields
        private readonly RegisterExpressions registers;
        private ILGenerator il;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the Addresser32 class.
        /// </summary>
        /// <param name="registers">Associated register expression generator.</param>
        public Addresser32(RegisterExpressions registers)
        {
            this.registers = registers;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Generates a 32-bit linear address from the provided operand.
        /// </summary>
        /// <param name="operand">Operand to generate linear address from.</param>
        /// <param name="prefixes">Prefixes which affect the instruction.</param>
        /// <param name="il">Associated ILGenerator instance.</param>
        public void GenerateLinearAddress(CodeOperand operand, PrefixState prefixes, ILGenerator il)
        {
            this.il = il;
            BuildFullAddress(operand, prefixes);
        }
        /// <summary>
        /// Generates the 32-bit offset of the address specified in the provided operand.
        /// </summary>
        /// <param name="operand">Operand to generate address offset from.</param>
        /// <param name="il">Associated ILGenerator instance.</param>
        public void GenerateOffset(CodeOperand operand, ILGenerator il)
        {
            this.il = il;
            BuildAddressOffset32(operand);
        }
        #endregion

        #region Private Static Methods
        /// <summary>
        /// Returns the default segment register for an operand.
        /// </summary>
        /// <param name="mode">Addressing mode of the operand.</param>
        /// <param name="baseRegister">Base value of the operand's SIB byte.</param>
        /// <returns>Default segment register for the operand.</returns>
        private static string GetDefaultSegment32(CodeMemoryBase mode, CodeSibRegister baseRegister)
        {
            if(mode == CodeMemoryBase.EBP || (mode == CodeMemoryBase.SIB && (baseRegister == CodeSibRegister.EBP || baseRegister == CodeSibRegister.ESP)))
                return "SS";
            else
                return "DS";
        }
        /// <summary>
        /// Returns the segment override register currently in effect.
        /// </summary>
        /// <param name="prefixes">Prefixes which affect the instruction.</param>
        /// <returns>Segment override register in effect if any; otherwise null.</returns>
        private static string GetOverrideSegment(PrefixState prefixes)
        {
            if((prefixes & PrefixState.CS) != 0)
                return "CS";
            else if((prefixes & PrefixState.DS) != 0)
                return "DS";
            else if((prefixes & PrefixState.SS) != 0)
                return "SS";
            else if((prefixes & PrefixState.ES) != 0)
                return "ES";
            else if((prefixes & PrefixState.FS) != 0)
                return "FS";
            else if((prefixes & PrefixState.GS) != 0)
                return "GS";
            else
                return null;
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Generates a 32-bit linear address from an operand.
        /// </summary>
        /// <param name="operand">Operand to get address of.</param>
        /// <param name="prefixes">Prefixes which affect the instruction.</param>
        private void BuildFullAddress(CodeOperand operand, PrefixState prefixes)
        {
            var segment = GetOverrideSegment(prefixes) ?? GetDefaultSegment32(operand.EffectiveAddress, operand.Base);

            this.registers.GenerateSegmentBaseValue(segment, il);
            BuildAddressOffset32(operand);
            il.Emit(OpCodes.Add);
        }
        /// <summary>
        /// Generates the 32-bit offset of the address.
        /// </summary>
        /// <param name="operand">Operand to determine offset of.</param>
        private void BuildAddressOffset32(CodeOperand operand)
        {
            switch(operand.EffectiveAddress)
            {
            case CodeMemoryBase.EAX:
                BuildAddOffset32("EAX", (int)operand.ImmediateValue);
                break;

            case CodeMemoryBase.EBP:
                BuildAddOffset32("EBP", (int)operand.ImmediateValue);
                break;

            case CodeMemoryBase.EBX:
                BuildAddOffset32("EBX", (int)operand.ImmediateValue);
                break;

            case CodeMemoryBase.ECX:
                BuildAddOffset32("ECX", (int)operand.ImmediateValue);
                break;

            case CodeMemoryBase.EDI:
                BuildAddOffset32("EDI", (int)operand.ImmediateValue);
                break;

            case CodeMemoryBase.EDX:
                BuildAddOffset32("EDX", (int)operand.ImmediateValue);
                break;

            case CodeMemoryBase.ESI:
                BuildAddOffset32("ESI", (int)operand.ImmediateValue);
                break;

            case CodeMemoryBase.SIB:
                BuildSibOffset32(operand);
                break;
            }
        }
        /// <summary>
        /// Generates a 32-bit SIB offset.
        /// </summary>
        /// <param name="operand">Operand containing the SIB address.</param>
        private void BuildSibOffset32(CodeOperand operand)
        {
            if(operand.Base == CodeSibRegister.None)
                il.LoadConstant((int)operand.ImmediateValue);
            else
            {
                this.registers.GenerateValue(operand.Base.ToString(), il);
                if(operand.ImmediateValue != 0)
                {
                    il.LoadConstant((int)operand.ImmediateValue);
                    il.Emit(OpCodes.Add);
                }
            }

            if(operand.Index != CodeSibRegister.None)
            {
                this.registers.GenerateValue(operand.Index.ToString(), il);
                if(operand.Scale > 1)
                {
                    il.LoadConstant(operand.Scale);
                    il.Emit(OpCodes.Shl);
                }

                il.Emit(OpCodes.Add);
            }

            il.Emit(OpCodes.Conv_U4);
        }
        /// <summary>
        /// Generates an offset by adding a register and a displacement.
        /// </summary>
        /// <param name="r1">Register to add.</param>
        /// <param name="displacement">Displacemeht value to add.</param>
        private void BuildAddOffset32(string r1, int displacement)
        {
            this.registers.GenerateValue(r1, il);
            if(displacement != 0)
            {
                il.Emit(OpCodes.Conv_I4);
                il.LoadConstant(displacement);
                il.Emit(OpCodes.Add);
                il.Emit(OpCodes.Conv_U4);
            }
        }
        #endregion
    }
}
